#include <mpi.h>
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
#include <sstream>

class MPI_Exception : public std::runtime_error
{
public:
	MPI_Exception(std::string const& msg)
	 : std::runtime_error(msg)
	{ }
};

//#define ENFORCE(x) (x);
#define ENFORCE(x) { enforce(x, __LINE__, __FILE__); }
void enforce(int error, long line, const char * file)
{
	if ( error != 0 )
	{
		std::stringstream ss;
		ss << "MPI Error: " << error << "\n";
		ss << "File: \"" << file << " - Line: " << line;
		throw MPI_Exception(ss.str());
	}
}

class MPI_Encapsulation
{
	std::size_t m_count;
	std::size_t m_number;
	
public:
	MPI_Encapsulation(int & argc, char ** argv[])
	{
		ENFORCE(MPI_Init(&argc, argv));
		int tmp;
		ENFORCE(MPI_Comm_size(MPI_COMM_WORLD, &tmp));
		this->m_count = static_cast<std::size_t>(tmp);
		ENFORCE(MPI_Comm_rank(MPI_COMM_WORLD, &tmp));
		this->m_number = static_cast<std::size_t>(tmp);
	}
	
	~MPI_Encapsulation()
	{
		MPI_Finalize();
	}
	
	std::size_t getProcessCount() const
	{
		return this->m_count;
	}
	
	std::size_t getProcessNumber() const
	{
		return this->m_number;
	}
	
	bool isParentProcess() const
	{
		return this->getProcessNumber() == 0;
	}
	
public:
	template<class T>
	void receive(std::vector<T> & buffer, std::size_t sender) const
	{
		MPI_Status status;
		ENFORCE(MPI_Recv(&buffer[0], buffer.size(), MPI_INT, sender, 0, MPI_COMM_WORLD, &status)); /*todo: "MPI_INT" eventuell aendern, je nach template parameter*/
	}
	
	template<class T>
	void send(std::vector<T> const& buffer, std::size_t receiver) const
	{
		ENFORCE(MPI_Send(const_cast<T*>(&buffer[0]), buffer.size(), MPI_INT, receiver, 0, MPI_COMM_WORLD));
	}
	
	/*template<class T>
	void gather(std::vector<T> & buffer)
	{
		MPI_Gather(&buffer[0], buffer.size(), MPI_INT, &buffer[0], buffer.size(), MPI_INT, this->m_number, MPI_COMM_WORLD);
	}*/
};


int main(int argc, char * argv[])
{
	MPI_Encapsulation mpi(argc, &argv);
	
	std::vector<int> vec(mpi.getProcessCount(), 0);
	
	if ( mpi.isParentProcess() )
	{
		std::cout << "Starts with " << mpi.getProcessCount() << " Processes." << std::endl;
	
		vec[mpi.getProcessNumber()] = mpi.getProcessNumber() + 1;
		
		mpi.send(vec, (mpi.getProcessNumber() + 1) % mpi.getProcessCount());
		
		mpi.receive(vec, (mpi.getProcessCount() + mpi.getProcessNumber() - 1) % mpi.getProcessCount());
		
		bool success = true;
		for(std::size_t i = 0; i < vec.size(); ++i)
			if ( vec[i] != i+1 )
				success = false;
		
		std::cout << "Finished with " << (success ? "success" : "fails") << std::endl;
	}
	else
	{
		mpi.receive(vec, mpi.getProcessNumber()-1);
		
		vec[mpi.getProcessNumber()] = mpi.getProcessNumber() + 1;
		
		mpi.send(vec, (mpi.getProcessNumber() + 1) % mpi.getProcessCount());
	}
	
	return 0;
}
